/*
 * ngx_http_jjyun_module.c
 *
 *  Created on: 2018年3月23日
 *      Author: cfm
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_string.h>
#include <ngx_http.h>
#include "fdfs_client.h"
#include "trunk_shared.h"
#include "local_ip_func.h"
#include "fdfs_shared_func.h"
#include "fdfs_http_shared.h"
#include "http_func.h"
#include "logger.h"

#define JJYUN_UPLOAD "upload"
#define JJYUN_DELETE "delete"
#define JJYUN_DOWNLOAD "download"

#define JJYUN_ERROR_AGAIN 1 //当前请求未完成
#define JJYUN_ERROR_FAIL -1 //失败

typedef struct fdfs_groupstore_path_s
{
	char group_name[FDFS_GROUP_NAME_MAX_LEN + 1];
	int group_name_len;
	int storage_server_port;
	FDFSStorePaths store_paths;
} fdfs_groupstore_path_t;

static int storage_server_port = FDFS_STORAGE_SERVER_DEF_PORT;
static int my_group_name_len = 0;
static char my_group_name[FDFS_GROUP_NAME_MAX_LEN + 1] = {0};
static int group_count = 0;								 //for multi groups
static fdfs_groupstore_path_t *group_store_paths = NULL; //for multi groups
static int storage_sync_file_max_delay = 24 * 3600;
static bool use_storage_id = false;

typedef struct ngx_http_jjyun_conf_s
{
	ngx_str_t ngx_jjyun_module;
	ngx_path_t *store_path;
	ngx_path_t *state_store_path;
} ngx_http_jjyun_conf_t;

typedef struct ngx_http_jjyun_ctx_s
{
	void *type_ctx;
	ngx_buf_t *response;
	ngx_http_request_t *r;
} ngx_http_jjyun_ctx_t;

static ngx_int_t ngx_http_jjyun_process_init(ngx_cycle_t *cycle);
static void ngx_http_jjyun_process_exit(ngx_cycle_t *cycle);

static void *ngx_http_jjyun_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_jjyun_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);

static char *ngx_http_jjyun(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static ngx_int_t jjyun_set_header(ngx_http_request_t *r, const char *key, const char *low_key, const int key_len,
								  char *value, const int value_len);
static int jjyun_send_reply_chunk(ngx_http_request_t *r, const bool last_buf, const char *buff, const int size);
static int jjyun_send_file(ngx_http_request_t *r, const char *filename, const int filename_len,
						   const int64_t file_offset, const int64_t download_bytes);
static ngx_int_t jjyun_format_response(ngx_http_request_t *r, ngx_buf_t **buf, ngx_int_t ret, const char *fmt, ...);
static ngx_int_t jjyun_send_response(ngx_http_jjyun_ctx_t *jjctx);
static ngx_int_t ngx_http_jjyun_handler(ngx_http_request_t *r);

static ngx_command_t ngx_http_jjyun_commands[] = {

	{ngx_string("ngx_jjyun_module"),
	 NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1,
	 ngx_http_jjyun,
	 NGX_HTTP_LOC_CONF_OFFSET,
	 offsetof(ngx_http_jjyun_conf_t, ngx_jjyun_module),
	 NULL},

	{ngx_string("store_path"),
	 NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
	 ngx_conf_set_path_slot,
	 NGX_HTTP_LOC_CONF_OFFSET,
	 offsetof(ngx_http_jjyun_conf_t, store_path),
	 NULL},

	{ngx_string("state_store_path"),
	 NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
	 ngx_conf_set_path_slot,
	 NGX_HTTP_LOC_CONF_OFFSET,
	 offsetof(ngx_http_jjyun_conf_t, state_store_path),
	 NULL},
	ngx_null_command};

static ngx_http_module_t ngx_http_jjyun_module_ctx = {
	NULL, NULL, NULL, NULL, NULL, NULL,
	ngx_http_jjyun_create_loc_conf,
	ngx_http_jjyun_merge_loc_conf};

ngx_module_t ngx_http_jjyun_module = {
	NGX_MODULE_V1,
	&ngx_http_jjyun_module_ctx,
	ngx_http_jjyun_commands,
	NGX_HTTP_MODULE,
	NULL,						 /* init master */
	NULL,						 /* init module */
	ngx_http_jjyun_process_init, /* init process */
	NULL,						 /* init thread */
	NULL,						 /* exit thread */
	ngx_http_jjyun_process_exit, /* exit process */
	NULL,						 /* exit master */
	NGX_MODULE_V1_PADDING};

#include "rc4.c"
#include "jjyun_fdfs.c"
#include "jjyun_upload.c"
#include "jjyun_delete.c"
#include "jjyun_down.c"

static ngx_int_t jjyun_set_header(ngx_http_request_t *r, const char *key, const char *low_key, const int key_len,
								  char *value, const int value_len)
{
	ngx_table_elt_t *cc;

	cc = ngx_list_push(&r->headers_out.headers);
	if (cc == NULL)
	{
		return NGX_ERROR;
	}

	cc->hash = 1;
	cc->key.len = key_len;
	cc->key.data = (u_char *)key;
	cc->lowcase_key = (u_char *)low_key;
	cc->value.len = value_len;
	cc->value.data = (u_char *)value;

	return NGX_OK;
}

static int jjyun_send_reply_chunk(ngx_http_request_t *r, const bool last_buf, const char *buff, const int size)
{
	ngx_buf_t *b;
	ngx_chain_t out;
	ngx_int_t rc;
	u_char *new_buff;

	b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
	if (b == NULL)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc fail");
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	new_buff = ngx_palloc(r->pool, sizeof(u_char) * size);
	if (new_buff == NULL)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_palloc fail");
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	out.buf = b;
	out.next = NULL;

	memcpy(new_buff, buff, size);

	b->pos = (u_char *)new_buff;
	b->last = (u_char *)new_buff + size;
	b->memory = 1;
	b->last_in_chain = last_buf;
	b->last_buf = last_buf;

	rc = ngx_http_output_filter(r, &out);
	if (rc == NGX_OK || rc == NGX_AGAIN)
	{
		return NGX_OK;
	}
	else
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_output_filter fail, return code: %d", rc);
		return rc;
	}
}

static int jjyun_send_file(ngx_http_request_t *r, const char *filename, const int filename_len,
						   const int64_t file_offset, const int64_t download_bytes)
{
	ngx_http_core_loc_conf_t *ccf;
	ngx_buf_t *b;
	ngx_str_t ngx_filename;
	ngx_open_file_info_t of;
	ngx_chain_t out;
	ngx_uint_t level;
	ngx_int_t rc;

	ccf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

	ngx_filename.data = (u_char *)filename;
	ngx_filename.len = filename_len;

	ngx_memzero(&of, sizeof(ngx_open_file_info_t));

	of.read_ahead = ccf->read_ahead;
	of.directio = ccf->directio;
	of.valid = ccf->open_file_cache_valid;
	of.min_uses = ccf->open_file_cache_min_uses;
	of.errors = ccf->open_file_cache_errors;
	of.events = ccf->open_file_cache_events;
	if (ngx_open_cached_file(ccf->open_file_cache, &ngx_filename, &of, r->pool) != NGX_OK)
	{
		switch (of.err)
		{
		case 0:
			return NGX_HTTP_INTERNAL_SERVER_ERROR;
		case NGX_ENOENT:
		case NGX_ENOTDIR:
		case NGX_ENAMETOOLONG:
			level = NGX_LOG_ERR;
			rc = NGX_HTTP_NOT_FOUND;
			break;
		case NGX_EACCES:
			level = NGX_LOG_ERR;
			rc = NGX_HTTP_FORBIDDEN;
			break;
		default:
			level = NGX_LOG_CRIT;
			rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
			break;
		}

		if (rc != NGX_HTTP_NOT_FOUND || ccf->log_not_found)
		{
			ngx_log_error(level, r->connection->log, of.err, "%s \"%s\" failed", of.failed, filename);
		}

		return rc;
	}

	if (!of.is_file)
	{
		ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, "\"%s\" is not a regular file", filename);
		return NGX_HTTP_NOT_FOUND;
	}

	b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
	if (b == NULL)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc fail");
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
	if (b->file == NULL)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	out.buf = b;
	out.next = NULL;

	b->file_pos = file_offset;
	b->file_last = file_offset + download_bytes;
	b->in_file = download_bytes > 0 ? 1 : 0;
	b->file->fd = of.fd;
	b->file->name.data = (u_char *)filename;
	b->file->name.len = filename_len;
	b->file->log = r->connection->log;
	b->file->directio = of.is_directio;

	b->last_in_chain = 1;
	b->last_buf = 1;

	rc = ngx_http_output_filter(r, &out);
	if (rc == NGX_OK || rc == NGX_AGAIN)
	{
		return NGX_OK;
	}
	else
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_output_filter fail, return code: %d", rc);
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
}

static ngx_int_t jjyun_format_response(ngx_http_request_t *r, ngx_buf_t **buf, ngx_int_t ret, const char *fmt, ...)
{
	u_char tmp[512] = {0};
	va_list args;
	ngx_buf_t *b;
	if (r == NULL)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	va_start(args, fmt);
	ngx_vslprintf(tmp, tmp + 512, fmt, args);
	va_end(args);

	b = ngx_create_temp_buf(r->pool, 30 + ngx_strlen(tmp));
	if (b == NULL)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	ngx_sprintf(b->pos, "{\"response\":\"%d\",\"des\":\"%s\"}%Z", ret, tmp);
	b->last = b->pos + ngx_strlen(b->pos);
	b->last_buf = 1;

	*buf = b;

	return NGX_OK;
}

static ngx_int_t jjyun_send_response(ngx_http_jjyun_ctx_t *jjctx)
{
	ngx_int_t rc;
	ngx_http_request_t *r;
	ngx_buf_t *b;
	if (jjctx == NULL)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
	r = jjctx->r;
	if (r == NULL)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
	b = jjctx->response;
	if (b == NULL)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	ngx_str_set(&r->headers_out.content_type, "text/plain");
	r->headers_out.status = NGX_HTTP_OK;
	r->headers_out.content_length_n = b->last - b->pos;

	rc = ngx_http_send_header(r);
	if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
	{
		return rc;
	}
	ngx_chain_t out;
	out.buf = b;
	out.next = NULL;
	return ngx_http_output_filter(r, &out);
}

static ngx_int_t ngx_http_jjyun_handler(ngx_http_request_t *r)
{
	ngx_int_t rc = NGX_OK;
	ngx_http_jjyun_ctx_t *jjctx;

	if (!(r->method & (NGX_HTTP_POST | NGX_HTTP_GET | NGX_HTTP_HEAD)))
	{
		return NGX_HTTP_NOT_ALLOWED;
	}

	ngx_str_t t_resp;
	if (ngx_http_arg(r, (u_char *)"type", ngx_strlen("type"), &t_resp) != NGX_OK)
	{
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	jjctx = ngx_http_get_module_ctx(r, ngx_http_jjyun_module);

	if (jjctx == NULL)
	{
		jjctx = ngx_pcalloc(r->pool, sizeof(ngx_http_jjyun_ctx_t));
		if (jjctx == NULL)
		{
			return NGX_HTTP_INTERNAL_SERVER_ERROR;
		}
		ngx_http_set_ctx(r, jjctx, ngx_http_jjyun_module);
	}
	jjctx->r = r;

	if (0 == ngx_strncasecmp(t_resp.data, (u_char *)JJYUN_UPLOAD, t_resp.len))
	{
		//执行上传文件
		rc = jjyun_upload_handler(jjctx);
	}
	else if (0 == ngx_strncasecmp(t_resp.data, (u_char *)JJYUN_DELETE, t_resp.len))
	{
		//执行删除文件
		rc = ngx_http_discard_request_body(r);
		if (rc != NGX_OK)
		{
			return rc;
		}
		rc = jjyun_delete_handler(jjctx);
	}
	else if (0 == ngx_strncasecmp(t_resp.data, (u_char *)JJYUN_DOWNLOAD, t_resp.len))
	{
		//执行文件下载
		rc = ngx_http_discard_request_body(r);
		if (rc != NGX_OK)
		{
			return rc;
		}
		rc = jjyun_down_handler(jjctx);
	}
	return rc;
}

static ngx_path_init_t ngx_http_upload_temp_path = {
	ngx_string(NGX_HTTP_PROXY_TEMP_PATH), {1, 2, 0}};

static int fdfs_load_groups_store_paths(IniContext *pItemContext)
{
	char section_name[64];
	char *pGroupName;
	int bytes;
	int result;
	int i;

	bytes = sizeof(fdfs_groupstore_path_t) * group_count;
	group_store_paths = (fdfs_groupstore_path_t *)malloc(bytes);
	if (group_store_paths == NULL)
	{
		logError("file: "__FILE__
				 ", line: %d, "
				 "malloc %d bytes fail, "
				 "errno: %d, error info: %s",
				 __LINE__, bytes, errno, STRERROR(errno));
		return errno != 0 ? errno : ENOMEM;
	}

	for (i = 0; i < group_count; i++)
	{
		sprintf(section_name, "group%d", i + 1);
		pGroupName = iniGetStrValue(section_name, "group_name",
									pItemContext);
		if (pGroupName == NULL)
		{
			logError("file: "__FILE__
					 ", line: %d, "
					 "section: %s, you must set parameter: "
					 "group_name!",
					 __LINE__, section_name);
			return ENOENT;
		}

		group_store_paths[i].storage_server_port = iniGetIntValue(
			section_name, "storage_server_port", pItemContext,
			FDFS_STORAGE_SERVER_DEF_PORT);

		group_store_paths[i].group_name_len = snprintf(
			group_store_paths[i].group_name,
			sizeof(group_store_paths[i].group_name),
			"%s", pGroupName);
		if (group_store_paths[i].group_name_len == 0)
		{
			logError("file: "__FILE__
					 ", line: %d, "
					 "section: %s, parameter: group_name "
					 "can't be empty!",
					 __LINE__, section_name);
			return EINVAL;
		}

		group_store_paths[i].store_paths.paths =
			storage_load_paths_from_conf_file_ex(pItemContext,
												 section_name, false, &group_store_paths[i].store_paths.count,
												 &result);
		if (result != 0)
		{
			return result;
		}
	}

	return 0;
}

static ngx_int_t fdfs_mod_init()
{
	ngx_int_t result;
	IniContext iniContext;
	char *pLogFilename;
	bool load_fdfs_parameters_from_tracker = false;
	log_init();
	trunk_shared_init();

	if ((result = iniLoadFromFile(FDFS_MOD_CONF_FILENAME, &iniContext)) != 0)
	{
		logError("file: "__FILE__
				 ", line: %d, "
				 "load conf file \"%s\" fail, ret code: %d",
				 __LINE__, FDFS_MOD_CONF_FILENAME, result);
		return result;
	}
	do
	{
		load_log_level(&iniContext);

		pLogFilename = iniGetStrValue(NULL, "log_filename", &iniContext);
		if (pLogFilename != NULL && *pLogFilename != '\0')
		{
			if ((result = log_set_filename(pLogFilename)) != 0)
			{
				break;
			}
		}

		group_count = iniGetIntValue(NULL, "group_count", &iniContext, 0);
		if (group_count < 0)
		{
			logError("file: "__FILE__
					 ", line: %d, "
					 "conf file: %s, group_count: %d is invalid!",
					 __LINE__, FDFS_MOD_CONF_FILENAME, group_count);
			return EINVAL;
		}
		if (group_count > 0)
		{
			if ((result = fdfs_load_groups_store_paths(&iniContext)) != 0)
			{
				break;
			}
		}
		else
		{
			char *pGroupName;

			pGroupName = iniGetStrValue(NULL, "group_name", &iniContext);
			if (pGroupName == NULL)
			{
				logError("file: "__FILE__
						 ", line: %d, "
						 "config file: %s, you must set parameter: "
						 "group_name!",
						 __LINE__, FDFS_MOD_CONF_FILENAME);
				result = ENOENT;
				break;
			}

			my_group_name_len = snprintf(my_group_name, sizeof(my_group_name), "%s", pGroupName);
			if (my_group_name_len == 0)
			{
				logError("file: "__FILE__
						 ", line: %d, "
						 "config file: %s, parameter: group_name "
						 "can't be empty!",
						 __LINE__,
						 FDFS_MOD_CONF_FILENAME);
				result = EINVAL;
				break;
			}

			if ((result = storage_load_paths_from_conf_file(&iniContext)) != 0)
			{
				break;
			}
		}

		storage_server_port = iniGetIntValue(NULL, "storage_server_port", &iniContext, FDFS_STORAGE_SERVER_DEF_PORT);

		load_fdfs_parameters_from_tracker = iniGetBoolValue(NULL, "load_fdfs_parameters_from_tracker", &iniContext, false);
		if (load_fdfs_parameters_from_tracker)
		{
			result = fdfs_load_tracker_group_ex(&g_tracker_group, FDFS_MOD_CONF_FILENAME, &iniContext);
		}
		else
		{
			storage_sync_file_max_delay = iniGetIntValue(NULL, "storage_sync_file_max_delay", &iniContext, 24 * 3600);
			use_storage_id = iniGetBoolValue(NULL, "use_storage_id", &iniContext, false);
			if (use_storage_id)
			{
				result = fdfs_load_storage_ids_from_file(FDFS_MOD_CONF_FILENAME, &iniContext);
			}
		}

	} while (false);

	iniFreeContext(&iniContext);
	if (result != 0)
	{
		return result;
	}

	load_local_host_ip_addrs();

	return 0;
}
static ngx_int_t fdfs_mod_unint()
{
	fdfs_client_destroy();
	return 0;
}

static ngx_int_t ngx_http_jjyun_process_init(ngx_cycle_t *cycle)
{
	int result;
	fprintf(stderr, "ngx_http_jjyun_process_init pid=%d\n", getpid());
	//初始化标签
	int i = 3600 * 24;
	i = i * 365 * 100;
	int64_t g_intfdfssendtime = i;
	char chfdfssendtime[FDFS_CLIENT_ECVRYPT_LEN] = {0};
	long2buff(g_intfdfssendtime, chfdfssendtime);
	Encrypt(chfdfssendtime, g_chfdfssendtime, sizeof(g_chfdfssendtime));
	// do some init here
	if ((result = fdfs_mod_init()) != 0)
	{
		return NGX_ERROR;
	}
	return NGX_OK;
}

static void ngx_http_jjyun_process_exit(ngx_cycle_t *cycle)
{
	fprintf(stderr, "ngx_http_jjyun_process_exit pid=%d\n", getpid());
	fdfs_mod_unint();
	return;
}

static void *ngx_http_jjyun_create_loc_conf(ngx_conf_t *cf)
{
	ngx_http_jjyun_conf_t *conf;
	conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_jjyun_conf_t));
	if (conf == NULL)
	{
		return NGX_CONF_ERROR;
	}
	return conf;
}

static char *ngx_http_jjyun_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
	ngx_http_jjyun_conf_t *prev = parent;
	ngx_http_jjyun_conf_t *conf = child;
	ngx_conf_merge_str_value(conf->ngx_jjyun_module, prev->ngx_jjyun_module, "");
	ngx_conf_merge_path_value(cf, &conf->store_path, prev->store_path, &ngx_http_upload_temp_path);
	ngx_conf_merge_path_value(cf, &conf->state_store_path, prev->state_store_path, &ngx_http_upload_temp_path);
	return NGX_CONF_OK;
}

static char *ngx_http_jjyun(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	ngx_http_core_loc_conf_t *clcf;
	clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
	clcf->handler = ngx_http_jjyun_handler;
	return NGX_CONF_OK;
}
